// TODO 分命令: 把4个路灯指令与9个LED灯指令分开, 这样可以确定路灯是否工作
/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * <h2><center>&copy; Copyright (c) 2022 CSTX.
 * All rights reserved.</center></h2>
 *
 * This software component is licensed by ST under BSD 3-Clause license,
 * the "License"; You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at:
 *                        opensource.org/licenses/BSD-3-Clause
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "dht11.h"
#include "adc.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <stdlib.h> // atof方法的头文件
#include "string.h"
#include "oled.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

// 调试指令
#define GET_BTN1_PIN "btn" // 获取按钮电平指令

// 数据请求指令
#define GET_TEMPERATURE_HUMIDITY "th" // 获取温湿度的指令
#define GET_ADC_VALUE "adc"           // 获取光照强度的指令

// 光照强度指令

#define STREET_LAMP1_ON "street_lamp1_on"
#define STREET_LAMP2_ON "street_lamp2_on"
#define STREET_LAMP_ON "street_lamp_on"
#define STREET_LAMP_OFF "street_lamp_off"

// led 指令
#define LED_ON "led_on"
#define LED_OFF "led_off"
#define LED_FLASH "led_flash"

#define LED1_ON "led1_on"   // 较暗灯
#define LED1_OFF "led1_off" // 较暗灯
#define LED1_FLASH "led1_flash"

#define LED2_ON "led2_on"   // 较亮灯
#define LED2_OFF "led2_off" // 较亮灯
#define LED2_FLASH "led2_flash"
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
unsigned char disOLED_T_H[24]; // 要显示在采集温湿度的当前板液晶屏上的文字 (发送机使用)
unsigned char disOLED_ADC[24]; // 要显示在发送光照强度请求的板的液晶屏上的文字 (接收机使用)

char *received_H_T[2];         // 用来装分割后的 接收到的 温湿度字符串的 数组 (接收机使用). 格式: {湿度, 温度}
char *sent_H_T[2];             // 用来装分割后的 发送完之后的 温湿度字符串的 数组 (发送机使用). 格式: {湿度, 温度}
char *sent_H_T_format_str[12]; // 用来装格式化好的温湿度, 用于发送 (这里面是温湿度连在一起的)

int is_received_H_normal = 1; // 湿度异常标志: 1 表示正常, 0 表示异常
int is_received_T_normal = 1; // 温度异常标志: 1 表示正常, 0 表示异常

int is_sent_H_normal = 1; // 湿度异常标志: 1 表示正常, 0 表示异常
int is_sent_T_normal = 1; // 温度异常标志: 1 表示正常, 0 表示异常

int is_break_from_double_LED_warning = 0; // 判断上次闪烁报警是否为两灯闪烁, 用于确保从两灯闪烁过渡到单灯闪烁时另一个灯为熄灭状态

// 用于记住闪烁前路灯状态
GPIO_PinState street_lamp1_state = GPIO_PIN_SET; // 一开始默认两个路灯是灭的 (LED 1 较暗)
GPIO_PinState street_lamp2_state = GPIO_PIN_SET; // 一开始默认两个路灯是灭的 (LED 2 较亮)

int is_awaiting_assign_led_status = 0; // 标志: 路灯状态是否等待赋值

uint16_t ADC_Value = 0; // 光照强度 (发送机使用)
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
// 通用函数

void reset_USART_BUF_RX_STA(void);
void reset_USART2_BUF_RX_STA(void);
int is_instruction_valid(char *instruction);
void remember_street_lamps_state(void);

// 温湿度相关函数

void send_temperature_humidity(void);
void show_H_T(void);
void judge_H_T(double, double, int *, int *); // ! 特例: 收发两端共用 (共用是为了接收机两灯都在报警闪烁的时候, 发送机拒收电脑信息. 因为此时接收机两个灯都报警, 没有多余的灯可以利用)
void flash_led(void);
void cancel_flash(void);

// 光照强度相关函数

void send_ADC_value(void);
void show_ADC_value(void);
void judge_ADC_value(void);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
void SystemClock_Config(void);

unsigned char cscxReg[3] = {0xC1, 0x00, 0x09}; // 发送查询数据
unsigned char csrevReg[12] = {0xC1, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
unsigned char key = 0;

/**
 * @brief  The application entry point.
 * @retval int
 */
int main(void)
{
    /* USER CODE BEGIN 1 */
    /* USER CODE END 1 */
    int i;
    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */
    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */
    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_ADC1_Init();
    MX_USART1_UART_Init();
    MX_USART2_UART_Init();
    /* USER CODE BEGIN 2 */
    USART_Interupt_Enable(); // 使能串口接收中断和空闲中断
    HAL_ADCEx_Calibration_Start(&hadc1);

    printf("LORA Board LED OK\r\n");

    CS_OLED_Init();
    OLED_Clear();

    // OLED_ShowCHinese(20, 3, 15); // 第
    // OLED_ShowCHinese(36, 3, 16); // 四
    // OLED_ShowCHinese(52, 3, 17); // 组
    // OLED_ShowCHinese(68, 3, 18); // 最
    // OLED_ShowCHinese(84, 3, 19); // 强

    printf("LORA Board LCD OK\r\n");
    /* USER CODE END 2 */

    cstxInitialize(); // 初始化LED  闪烁下

    HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
    for (i = 0; i < 8; i++) // 闪烁下 搬到这里来了
    {
        HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); // 切换灯1
        HAL_Delay(80);
        HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin); // 切换灯2
    }
    HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);

    if (HAL_GPIO_ReadPin(M1_GPIO_Port, M1_Pin) == 1) // M1 是高电平才开始读取模块的寄存器
    {
        printf("\r\n M1 = 1 ; M0 = 0 Register mode \r\n");
        CS_Reg_Send_Data(cscxReg, 3); // ! 发送3个字节 (可能罪魁祸首)
        HAL_Delay(300);               // 等待延迟
        printf("\r\n\r\nLORA REG CODE %d UART2->", regConut);
        for (i = 0; i < 12; i++)
        {
            printf("%02X", USART2_RX_BUF[i]);
            printf(" ");
        }
        cstx_reg_Receive_Data(csrevReg, &key);
        printf("\r\n\r\nLORA REG CODE %d REG->", key);
        memset(disOLED_T_H, 0, 24);
        for (i = 0; i < 12; i++)
        {
            printf("%02X", csrevReg[i]);
            printf(" ");
        }

        OLED_ShowString(0, 2, "Addr:");
        for (i = 3; i < 5; i++) // 显示
        {
            sprintf((char *)disOLED_T_H, "%02X", csrevReg[i]);
            printf("disOLED=%s,", disOLED_T_H);
            OLED_ShowString(i * 16 - 4, 2, disOLED_T_H);
        }

        OLED_ShowString(82, 2, "Ch:");
        sprintf((char *)disOLED_T_H, "%02X", csrevReg[8]);
        printf("disOLED=%s\r\n", disOLED_T_H);
        OLED_ShowString(104, 2, disOLED_T_H);
    }
    else
    {
        printf("\r\n M1 = 0; M0 = 0 Transparent mode \r\n");
    }

    HAL_GPIO_WritePin(M1_GPIO_Port, M1_Pin, GPIO_PIN_RESET); // 使模块能运行 PA1

    /* ----------------------------------------------USER CODE BEGIN 3-------------------------------------------- */
    OLED_Clear(); // 清屏

    /* ----------------------------------------------接收机 初始化 BEGIN-------------------------------------------- */
    USART2_RX_STA = REC_WAIT; // 0 将lora接收状态设置为 0. (接收板在打开时默认 USART2_RX_STA 是 REC_OK 状态, 所以在我还没有发送指令时, 接收板依然可以展示一些错误的数据)

    int is_receiving_T_H = 0;       // 当前是否处于接收温湿度状态
    int is_receiving_ADC_value = 0; // 当前是否处于接收光照强度状态

    int is_awaiting_flash_led = 0; // 标志: 接收机收到的温湿度是否等待闪烁

    // 屏幕初始化信息 BEGIN
    OLED_ShowString(0, 2, "ADC:......");

    OLED_ShowCHinese(0, 6, 1);  // humidity 湿
    OLED_ShowCHinese(18, 6, 2); // 度
    OLED_ShowString(36, 6, ":");
    OLED_ShowString(50, 6, "....");

    OLED_ShowCHinese(0, 4, 0);  // temperature 温
    OLED_ShowCHinese(18, 4, 2); // 度
    OLED_ShowString(36, 4, ":");
    OLED_ShowString(50, 4, "....");
    // 屏幕初始化信息 END
    /* ----------------------------------------------接收机 初始化 END-------------------------------------------- */

    // 接收机与发送机共用的变量 (用于处理 温湿度闪烁报警 与 光照强度LED灯长亮 的关系)
    int is_break_from_warning = 0; // 标志: 是否刚从闪烁报警状态跳出

    int is_street_lamp_working = 0; // 标志: 当前路灯是否正在工作(长亮)

    int is_break_from_double_LED_flash = 0; // 标志: 是否刚从双LED灯闪烁状态跳出 (是普通led指令的闪烁, 不是报警闪烁)

    while (1)
    {
        /* ----------------------------------------------发送机 (发送传感器数据的) BEGIN-------------------------------------------- */
        OLED_ShowString(0, 0, "Send data ......");

        // * 对接收板转发的电脑指令的响应
        if (strstr((char *)USART2_RX_BUF, GET_TEMPERATURE_HUMIDITY)) // 如果收到温湿度请求, 则发送h湿度和t温度
        {
            printf("\r\n\nReceived temperature and humidity request: %s\r", USART2_RX_BUF);

            reset_USART2_BUF_RX_STA(); // 清除 LORA 缓冲区数组并将其接收状态设置为 0

            send_temperature_humidity(); // 发送温湿度数据

            // 这一段用于判断发送机的温湿度正常与否, 温湿度 都 异常的话发送机会拒收电脑信息
            sent_H_T[0] = strtok((char *)sent_H_T_format_str, ","); // 解析 发送机 humidity 湿度数据
            sent_H_T[1] = strtok(NULL, ",");                        // 解析 发送机 temperature 温度数据

            judge_H_T(atof(sent_H_T[0]), atof(sent_H_T[1]), &is_sent_H_normal, &is_sent_T_normal);
        }
        else if (strstr((char *)USART2_RX_BUF, GET_ADC_VALUE)) // 如果收到光照强度请求, 则发送光照强度
        {
            printf("\r\n\nReceived light intensity request: %s\r\n", USART2_RX_BUF);

            reset_USART2_BUF_RX_STA(); // 清除 LORA 缓冲区数组并将其接收状态设置为 0

            send_ADC_value(); // 发送光照强度
        }

        // * 对路灯指令的判断
        if (strstr((char *)USART2_RX_BUF, STREET_LAMP1_ON)) // 较暗的那个路灯 (led1) 亮, 同时关闭另外一个路灯
        {
            printf("\r\nExecute street lamp instruction (turn on the darker LED): %s\r\n", USART2_RX_BUF);

            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); // 亮灯1
            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);   // 灭灯2

            is_street_lamp_working = 1;         // 当前路灯正在工作
            is_break_from_double_LED_flash = 0; // 现在是路灯长亮, 不是普通LED灯闪烁

            reset_USART2_BUF_RX_STA(); // 清除 LORA 缓冲区数组并将其接收状态设置为 0
        }
        else if (strstr((char *)USART2_RX_BUF, STREET_LAMP2_ON)) // 较亮的那个路灯 (led2) 亮, 同时关闭另外一个路灯
        {
            printf("\r\nExecute street lamp instruction (turn on the lighter LED): %s\r\n", USART2_RX_BUF);

            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET); // 亮灯2
            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);   // 灭灯1

            is_street_lamp_working = 1;         // 当前路灯正在工作
            is_break_from_double_LED_flash = 0; // 现在是路灯长亮, 不是普通LED灯闪烁

            reset_USART2_BUF_RX_STA(); // 清除 LORA 缓冲区数组并将其接收状态设置为 0
        }
        else if (strstr((char *)USART2_RX_BUF, STREET_LAMP_ON)) // 两个路灯亮
        {
            printf("\r\nExecute street lamp instruction (turn on both LED): %s\r\n", USART2_RX_BUF);

            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); // 亮灯1
            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET); // 亮灯2

            is_street_lamp_working = 1;         // 当前路灯正在工作
            is_break_from_double_LED_flash = 0; // 现在是路灯长亮, 不是普通LED灯闪烁

            reset_USART2_BUF_RX_STA(); // 清除 LORA 缓冲区数组并将其接收状态设置为 0
        }
        else if (strstr((char *)USART2_RX_BUF, STREET_LAMP_OFF)) // 两个路灯灭
        {
            printf("\r\nExecute street lamp instruction (turn off both LED): %s\r\n", USART2_RX_BUF);

            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); // 灭灯1
            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET); // 灭灯2

            is_street_lamp_working = 0;
            is_break_from_double_LED_flash = 0; // 现在是路灯模式, 不是普通LED灯闪烁

            reset_USART2_BUF_RX_STA(); // 清除 LORA 缓冲区数组并将其接收状态设置为 0
        }
        /* ----------------------------------------------发送机 (发送传感器数据的) END-------------------------------------------- */

        //  * 这里一大段是判断是否收到来自另一个板的电脑led指令, 不是发送机也不是接收机 BEGIN
        if (strstr((char *)USART2_RX_BUF, LED1_ON)) // led1 亮
        {
            printf("\r\nExecute LED instruction: %s\r\n", USART2_RX_BUF);

            if (is_street_lamp_working) // 如果路灯正在长亮
            {
                remember_street_lamps_state(); // 记住两个路灯状态
            }

            if (is_break_from_double_LED_flash) // 从普通双led闪烁状态跳出才要灭一次另外一个灯, 报警期间亮灯则不灭另外一个灯
            {
                HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET); // 灭灯2
                is_break_from_double_LED_flash = 0;
            }
            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); // 亮灯1

            reset_USART2_BUF_RX_STA(); // 清除 LORA 缓冲区数组并将其接收状态设置为 0

            is_street_lamp_working = 0; // 普通LED灯开始工作, 替代掉路灯
        }
        else if (strstr((char *)USART2_RX_BUF, LED1_OFF)) // led1 灭
        {
            printf("\r\nExecute LED instruction: %s\r\n", USART2_RX_BUF);

            if (is_street_lamp_working) // 如果路灯正在长亮
            {
                remember_street_lamps_state(); // 记住两个路灯状态
            }

            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); // 灭灯1

            reset_USART2_BUF_RX_STA(); // 清除 LORA 缓冲区数组并将其接收状态设置为 0

            is_street_lamp_working = 0; // 普通LED灯开始工作, 替代掉路灯
        }
        else if (strstr((char *)USART2_RX_BUF, LED1_FLASH)) // led1 闪烁
        {
            printf("\r\nExecute LED instruction: %s\r\n", USART2_RX_BUF);

            if (is_street_lamp_working) // 如果路灯正在长亮
            {
                remember_street_lamps_state(); // 记住两个路灯状态
            }

            if (is_break_from_double_LED_flash) // 从普通双led闪烁状态跳出才要灭一次另外一个灯, 报警期间亮灯则不灭另外一个灯
            {
                HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET); // 灭灯2
                is_break_from_double_LED_flash = 0;
            }
            HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); // 切换灯1

            is_street_lamp_working = 0; // 普通LED灯开始工作, 替代掉路灯

            HAL_Delay(80); // 闪烁延迟
        }
        else if (strstr((char *)USART2_RX_BUF, LED2_ON)) // led2 亮
        {
            printf("\r\nExecute LED instruction: %s\r\n", USART2_RX_BUF);

            if (is_street_lamp_working) // 如果路灯正在长亮
            {
                remember_street_lamps_state(); // 记住两个路灯状态
            }

            if (is_break_from_double_LED_flash) // 从普通双led闪烁状态跳出才要灭一次另外一个灯, 报警期间亮灯则不灭另外一个灯
            {
                HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); // 灭灯1
                is_break_from_double_LED_flash = 0;
            }
            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET); // 亮灯2

            reset_USART2_BUF_RX_STA(); // 清除 LORA 缓冲区数组并将其接收状态设置为 0

            is_street_lamp_working = 0; // 普通LED灯开始工作, 替代掉路灯
        }
        else if (strstr((char *)USART2_RX_BUF, LED2_OFF)) // led2 灭
        {
            printf("\r\nExecute LED instruction: %s\r\n", USART2_RX_BUF);

            if (is_street_lamp_working) // 如果路灯正在长亮
            {
                remember_street_lamps_state(); // 记住两个路灯状态
            }

            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET); // 灭灯2

            reset_USART2_BUF_RX_STA(); // 清除 LORA 缓冲区数组并将其接收状态设置为 0

            is_street_lamp_working = 0; // 普通LED灯开始工作, 替代掉路灯
        }
        else if (strstr((char *)USART2_RX_BUF, LED2_FLASH)) // led2 闪烁
        {
            printf("\r\nExecute LED instruction: %s\r\n", USART2_RX_BUF);

            if (is_street_lamp_working) // 如果路灯正在长亮
            {
                remember_street_lamps_state(); // 记住两个路灯状态
            }

            if (is_break_from_double_LED_flash) // 从普通双led闪烁状态跳出才要灭一次另外一个灯, 报警期间亮灯则不灭另外一个灯
            {
                HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); // 灭灯1
                is_break_from_double_LED_flash = 0;
            }
            HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin); // 切换灯2

            is_street_lamp_working = 0; // 普通LED灯开始工作, 替代掉路灯

            HAL_Delay(80); // 闪烁延迟
        }
        else if (strstr((char *)USART2_RX_BUF, LED_ON)) // 两个LED灯 亮
        {
            printf("\r\nExecute LED instruction: %s\r\n", USART2_RX_BUF);

            if (is_street_lamp_working) // 如果路灯正在长亮
            {
                remember_street_lamps_state(); // 记住两个路灯状态
            }

            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); // 亮灯1
            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET); // 亮灯2

            reset_USART2_BUF_RX_STA(); // 清除 LORA 缓冲区数组并将其接收状态设置为 0

            is_street_lamp_working = 0; // 普通LED灯开始工作, 替代掉路灯
        }
        else if (strstr((char *)USART2_RX_BUF, LED_OFF)) // 两个LED灯 灭
        {
            printf("\r\nExecute LED instruction: %s\r\n", USART2_RX_BUF);

            if (is_street_lamp_working) // 如果路灯正在长亮
            {
                remember_street_lamps_state(); // 记住两个路灯状态
            }

            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); // 灭灯1
            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET); // 灭灯2

            reset_USART2_BUF_RX_STA();

            is_street_lamp_working = 0; // 普通LED灯开始工作, 替代掉路灯
        }
        else if (strstr((char *)USART2_RX_BUF, LED_FLASH)) // 两个LED灯闪烁
        {
            is_break_from_double_LED_flash = 1;

            printf("\r\nExecute LED instruction: %s\r\n", USART2_RX_BUF);

            if (is_street_lamp_working) // 如果路灯正在长亮
            {
                remember_street_lamps_state(); // 记住两个路灯状态
            }

            HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); // 切换灯1
            HAL_Delay(80);
            HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin); // 切换灯2

            is_street_lamp_working = 0; // 普通LED灯开始工作, 替代掉路灯
        }
        //  * 这里一大段是判断是否收到来自另一个板的电脑led指令, 不是发送机也不是接收机 END

        /* ----------------------------------------------接收机 BEGIN-------------------------------------------- */
        // * 对发过来的传感器数据的处理 (需要分辨是adc还是温湿度)
        else if (USART2_RX_STA == REC_OK) // 若收到发送机发来的信息 (能进到这里的数据只能是传感器数据, 指令信息已经在上面排除掉了), 则将收到的数据显示在液晶屏上
        {
            if (is_receiving_T_H) // ! 非报警情况下获取到的正常温湿度数据不做led灯熄灭处理 (防止覆盖路灯状态: 正常报警恢复后会将亮着的路灯全部熄灭)
            {
                show_H_T();                                                                                            // 解析和展示收到的温湿度数据
                judge_H_T(atof(received_H_T[0]), atof(received_H_T[1]), &is_received_H_normal, &is_received_T_normal); // 判断温湿度是否正常

                is_receiving_T_H = 0; // 接收温湿度数据结束

                if (!(is_received_H_normal && is_received_T_normal)) // 如果收到异常温湿度, 则准备闪烁
                {
                    is_awaiting_flash_led = 1; // 开始等待 LED 灯报警闪烁
                }
            }
            else if (is_receiving_ADC_value)
            {
                show_ADC_value();  // 解析和展示收到的光照强度数据
                judge_ADC_value(); // 判断收到的光强处于哪个区间, 并发送对应开/关灯命令

                is_receiving_ADC_value = 0; // 接受光照强度数据结束
            }

            reset_USART2_BUF_RX_STA(); // 清除 LORA 缓冲区数组并将其接收状态设置为 0

            is_street_lamp_working = 0;
        }

        // * 温湿度报警闪烁
        if (is_awaiting_flash_led) // 闪烁 如果刚收到一次异常温湿度数据
        {
            if (is_street_lamp_working) // 如果路灯正在长亮
            {
                // 记住两个路灯状态
                remember_street_lamps_state();
            }

            flash_led(); // 根据温湿度异常情况进行闪烁报警

            is_break_from_double_LED_flash = 0; // 现在是报警灯闪烁, 不是普通LED灯闪烁

            is_awaiting_flash_led = 0; // 报警灯闪烁结束

            is_street_lamp_working = 0; //  ! 因为开始闪烁报警了, 所以路灯不再长亮, 而是化为报警灯开始闪烁
        }

        // * 准备从报警状态恢复正常
        if (is_break_from_warning && is_received_T_normal && is_received_H_normal) // 如果上次循环处于报警状态并且本次温湿度数据是正常的(也就是刚从报警状态中恢复正常), 则取消闪烁
        {
            if (is_awaiting_assign_led_status) // 如果正准备将路灯状态赋值给报警灯 (赋完之后至少有一个LED灯长亮)
            {
                printf("\r\nAbout to assign......\r\n");

                // 液晶屏消除感叹号 !!! 报警
                OLED_ShowString(96, 4, "   "); // t 温度
                OLED_ShowString(96, 6, "   "); // h 湿度

                HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, street_lamp1_state); // 将 LED 1 的状态设置为报警前的路灯 1 状态
                HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, street_lamp2_state); // 将 LED 2 的状态设置为报警前的路灯 2 状态

                // ! 已经成功赋值了
                printf("\r\nled1 pin: %s\r\n", HAL_GPIO_ReadPin(LED1_GPIO_Port, LED1_Pin) ? "off" : "on");
                printf("\r\nled2 pin: %s\r\n", HAL_GPIO_ReadPin(LED2_GPIO_Port, LED2_Pin) ? "off" : "on");

                is_awaiting_assign_led_status = 0; // 标志着结束赋值

                is_street_lamp_working = 1; // 路灯开始长亮

                is_break_from_warning = 0;
            }
            else // 这是在两路灯熄灭时报警恢复后的报警灯状态, 直接就恢复不亮的状态, 表示温湿度正常, 不报警
            {
                is_break_from_warning = 0;

                cancel_flash(); // 为正常的温湿度数据取消闪烁报警
            }
        }

        // * 温湿度闪烁报警自动发送请求
        if (!(is_received_H_normal && is_received_T_normal)) // 如果处于报警状态
        {
            USART2_printf(GET_TEMPERATURE_HUMIDITY);

            printf("\r\n\n--- Send Data (T_H warning): %s ---\r\n", GET_TEMPERATURE_HUMIDITY);

            is_receiving_T_H = 1;

            is_break_from_warning = 1;

            HAL_Delay(900); // todo 等待对方把数据发过来 (记得修改)
        }

        // * 对电脑指令的转发
        if (USART_RX_STA == REC_OK) // 转发: 如果接收到电脑的正确指令, 则发送, 否则忽略
        {
            //  如果处于报警, 则本机不接受adc指令, 其他都可以, 这样可以控制到另外一个板的led亮灭 (原先是所有命令都不接受)
            if (!(is_received_H_normal && is_received_T_normal))
            {
                if (!strcmp((char *)USART_RX_BUF, GET_ADC_VALUE)) // 如果是adc指令
                {
                    printf("\r\n\n!!! Currently in alarm state, cannot send GET_ADC_VALUE instructions !!!: %s\r\n\n", USART_RX_BUF);

                    reset_USART_BUF_RX_STA();

                    continue;
                }
            }

            if (!is_instruction_valid((char *)USART_RX_BUF)) // 如果电脑上发来错误指令, 则忽略
            {
                printf("\r\n\nInvalid instruction: %s\r\n", USART_RX_BUF);

                reset_USART_BUF_RX_STA(); // 清除 串口 缓冲区数组并将其接收状态设置为 0

                continue;
            }

            if (strstr((char *)USART_RX_BUF, GET_TEMPERATURE_HUMIDITY)) // 如果电脑上发来请求温湿度指令
            {
                // 规定即将收到的 LORA 信息要作为温湿度处理
                is_receiving_T_H = 1;
                is_receiving_ADC_value = 0;
            }
            else if (strstr((char *)USART_RX_BUF, GET_ADC_VALUE)) // 如果电脑上发来请求光照强度指令
            {
                // 规定即将收到的 LORA 信息要作为光照强度处理
                is_receiving_ADC_value = 1;
                is_receiving_T_H = 0;
            }

            printf("\r\n\n--- Send data: %s ---\r\n", USART_RX_BUF);

            USART2_printf("%s\r\n", USART_RX_BUF);

            reset_USART_BUF_RX_STA(); // 清除 串口 缓冲区数组并将其接收状态设置为 0
        }

        // * 按按钮
        if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0 && is_sent_H_normal && is_sent_T_normal) // 只有在确定对方温湿度正常不报警的时候才可以按按钮
        {
            HAL_Delay(100);
            if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0 && is_sent_H_normal && is_sent_T_normal)
            {
                printf("\r\nPressed button.\r\n");
                USART2_printf(LED_ON);
            }
        }
        /* ----------------------------------------------接收机 END-------------------------------------------- */
    }
    /* ----------------------------------------------USER CODE END 3-------------------------------------------- */
}

/* ----------------------------------------------USER CODE BEGIN 4-------------------------------------------- */

/* ----------------------------------------------通用函数 BEGIN-------------------------------------------- */

/**
 * @brief 记住此时两个路灯的状态
 * @note 记住由路灯指令导致的LED灯长亮时, 两灯的状态
 * @attention 必须在报警闪烁前使用, 闪烁报警完成后会恢复到上一次路灯状态, 而不是LED灯状态
 */
void remember_street_lamps_state(void)
{
    printf("\r\nStreet lamp is working.\r\n");

    // 记住此时两个路灯的状态
    printf("\r\nled1 pin: %s\r\n", HAL_GPIO_ReadPin(LED1_GPIO_Port, LED1_Pin) ? "off" : "on");
    printf("\r\nled2 pin: %s\r\n", HAL_GPIO_ReadPin(LED2_GPIO_Port, LED2_Pin) ? "off" : "on");

    street_lamp1_state = HAL_GPIO_ReadPin(LED1_GPIO_Port, LED1_Pin); // 读灯 1 状态并记录起来
    street_lamp2_state = HAL_GPIO_ReadPin(LED2_GPIO_Port, LED2_Pin); // 读灯 2 状态并记录起来

    is_awaiting_assign_led_status = 1; // 记住路灯状态之后, 等待温湿度恢复正常, 赋值给温湿度报警灯
}

/**
 * @brief 判断从电脑发过来的指令是否正确
 * @attention 里面用到的 strcmp(): 如果两个比较的字符串完全相同, 返回值为 0
 * @retval 0 或 1: 如果指令正确, 返回 1; 反之返回 0
 */
int is_instruction_valid(char *instruction)
{
    int is_valid = 0;

    // 传感器指令
    if (!(strcmp(instruction, GET_TEMPERATURE_HUMIDITY) && strcmp(instruction, GET_ADC_VALUE)))
    {
        is_valid = 1;
    }

    // 同时控制两个led指令
    else if (!(strcmp(instruction, LED_ON) && strcmp(instruction, LED_OFF) && strcmp(instruction, LED_FLASH)))
    {
        is_valid = 1;
    }

    // led1 指令
    else if (!(strcmp(instruction, LED1_ON) && strcmp(instruction, LED1_OFF) && strcmp(instruction, LED1_FLASH)))
    {
        is_valid = 1;
    }

    // led2 指令
    else if (!(strcmp(instruction, LED2_ON) && strcmp(instruction, LED2_OFF) && strcmp(instruction, LED2_FLASH)))
    {
        is_valid = 1;
    }

    return is_valid;
}

/**
 * @brief 清除 串口 缓冲区数组并将其接收状态设置为 0
 */
void reset_USART_BUF_RX_STA(void)
{
    memset(USART_RX_BUF, 0, USART_REC_LEN);
    USART_RX_STA = 0;
}

/**
 * @brief 清除 LORA 缓冲区数组并将其接收状态设置为 0
 */
void reset_USART2_BUF_RX_STA(void)
{
    memset(USART2_RX_BUF, 0, USART_REC_LEN);
    USART2_RX_STA = 0;
}
/* ----------------------------------------------通用函数 END-------------------------------------------- */

/* ----------------------------------------------光照强度相关函数 BEGIN-------------------------------------------- */

/**
 * @brief 发送光照强度
 */
void send_ADC_value(void)
{
    HAL_ADC_Start(&hadc1);                 // 启动ADC转换
    HAL_ADC_PollForConversion(&hadc1, 50); // 等待转换完成，时间为50ms

    if (HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC))
    {
        ADC_Value = HAL_ADC_GetValue(&hadc1); // 获取AD值

        USART2_printf("%d", ADC_Value); // ! 这里将光照强度发还给接收板

        printf("\r*** PA4 ADC = %d , Voltage = %.4f V ***\r\n", ADC_Value, ADC_Value * 3.3f / 4096);

        OLED_ShowString(0, 0, "Send data [OK]..");

        HAL_Delay(50); // 让 [OK] 消失得没有那么快
    }
}

/**
 * @brief 解析和展示收到的光照强度数据
 */
void show_ADC_value(void)
{
    float voltage = atof((char *)USART2_RX_BUF) * 3.3f / 4096;

    printf("\r\nReceived LORA ADC: %s, Voltage = %.2f V\r\n", USART2_RX_BUF, voltage);

    memset(disOLED_ADC, 0, 24);

    sprintf((char *)disOLED_ADC, "ADC:%.2fV, %s", voltage, USART2_RX_BUF); // 将光照强度和电压整理好格式放在一个数组里先, 等会将这个数组显示在液晶屏上

    OLED_ShowString(0, 2, "                "); // 清空显示屏上次的adc值

    OLED_ShowString(0, 2, (unsigned char *)disOLED_ADC); // 将光照强度和电压显示在液晶屏上
}

/**
 * @brief 判断收到的光强处于哪个区间, 并发送对应开/关灯命令
 */
void judge_ADC_value(void)
{
    int received_ADC_value = atoi((char *)USART2_RX_BUF);

    if (received_ADC_value < 400) // 大白天, 很亮, 两个灯都关 (不进行操作任何操作或拿闪光灯照它时)
    {
        USART2_printf(STREET_LAMP_OFF);

        printf("\r\nSend LED instruction: %s\r\n", STREET_LAMP_OFF);
    }
    else if (received_ADC_value >= 400 && received_ADC_value < 1500) // 下午, 有点亮, 较暗的那个灯 (led1) 亮 (一手指轻按)
    {
        USART2_printf(STREET_LAMP1_ON);

        printf("\r\nSend LED instruction: %s\r\n", STREET_LAMP1_ON);
    }
    else if (received_ADC_value >= 1500 && received_ADC_value < 3000) // 黄昏, 有点暗, 较亮的那个灯 (led2) 亮 (三手指头围着)
    {
        USART2_printf(STREET_LAMP2_ON);

        printf("\r\nSend LED instruction: %s\r\n", STREET_LAMP2_ON);
    }
    else if (received_ADC_value >= 3000) // 大晚上, 很暗, 两个灯都亮 (用手围住)
    {
        USART2_printf(STREET_LAMP_ON);

        printf("\r\nSend LED instruction: %s\r\n", STREET_LAMP_ON);
    }
}
/* ----------------------------------------------光照强度相关函数 END-------------------------------------------- */

/* ----------------------------------------------温湿度相关函数 BEGIN-------------------------------------------- */

/**
 * @brief 发送温湿度数据
 */
void send_temperature_humidity(void)
{
    if (DHT11_READ_DATA() == 1) // 发送温湿度信息给接收机
    {
        memset(sent_H_T_format_str, 0, 12); // 将 sent_H_T_format_str 数组内容设置为 0 (因为可能存储着上一次获取到的温湿度数据)

        sprintf((char *)sent_H_T_format_str, "%d.%d,%d.%d", Dht11data[0], Dht11data[1], Dht11data[2], Dht11data[3]); // 将要发送的温湿度整理好格式放在sent_H_T数组里

        USART2_printf((char *)sent_H_T_format_str); // ! 发送温湿度

        memset(disOLED_T_H, 0, 25); // 将 disOLEO 数组内容设置为 0 (因为可能存储着上一次获取到的温湿度数据)

        sprintf((char *)disOLED_T_H, "T:%d.%dC H:%d.%dR", Dht11data[2], Dht11data[3], Dht11data[0], Dht11data[1]); // 发送格式化字符串到 disOLED 数组

        printf("\r\n*** Send DHT11: %s ***\r\n", disOLED_T_H);

        OLED_ShowString(0, 0, "Send data [OK]..");
    }
}

/**
 * @brief 解析和展示收到的温湿度数据
 */
void show_H_T(void)
{
    received_H_T[0] = strtok((char *)USART2_RX_BUF, ","); // 解析 humidity 湿度数据
    received_H_T[1] = strtok(NULL, ",");                  // 解析 temperature 温度数据

    printf("\r\nReceived LORA humidity: %s%%RH, temperature:%sC\r\n", received_H_T[0], received_H_T[1]);

    OLED_ShowString(50, 6, (unsigned char *)received_H_T[0]); // 液晶屏显示 humidity 湿度数据
    OLED_ShowString(84, 6, "%");                              // 液晶屏显示百分号 (紧挨在湿度数据的后面)

    OLED_ShowString(50, 4, (unsigned char *)received_H_T[1]); // 液晶屏显示 temperature 温度数据
}

// TODO 已做修改, 方便收发两端共用
/**
 * @brief 判断温度、湿度是否正常
 * @param is_H_normal : 注意这个参数是指针 (传参需要取地址 &)
 * @param is_T_normal : 注意这个参数是指针 (传参需要取地址 &)
 * @attention 发送机发送完温湿度之后做判断, 接收机也会在接收完温湿度之后做判断.
 */
void judge_H_T(double H, double T, int *is_H_normal, int *is_T_normal)
{
    float max_humidity = 70.0;    // 湿度最大值
    float max_temperature = 24.0; // 温度最大值

    float min_humidity = 10.0;    // 湿度最小值
    float min_temperature = 10.0; // 温度最小值

    printf("\r\nJudging validity of temperature(%.2f - %.2f) and humidity(%.2f - %.2f%): ", min_temperature, max_temperature, min_humidity, max_humidity);

    *is_H_normal = (H > min_humidity && H < max_humidity) ? 1 : 0;
    *is_T_normal = (T > min_temperature && T < max_temperature) ? 1 : 0;

    printf("is_humidity_normal = %d, is_temperature_normal = %d\r\n", *is_H_normal, *is_T_normal);
}

/**
 * @brief 根据温湿度异常情况进行闪烁报警
 */
void flash_led(void)
{
    // 闪烁报警
    if (is_received_H_normal && !is_received_T_normal) // h湿度正常 t温度异常
    {
        // 液晶屏报警
        OLED_ShowString(96, 4, "!!!"); // t 温度
        OLED_ShowString(96, 6, "   "); // h 湿度

        if (is_break_from_double_LED_warning) // 如果上一次闪烁是两灯报警, 则这一次单灯闪烁要保证另外一个灯是灭的
        {
            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); // 灭灯1
            is_break_from_double_LED_warning = 0;
        }
        HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin); // 切换灯2
    }
    else if (!is_received_H_normal && is_received_T_normal) // h湿度异常 t温度正常
    {
        // 液晶屏报警
        OLED_ShowString(96, 4, "   "); // t 温度
        OLED_ShowString(96, 6, "!!!"); // h 湿度

        if (is_break_from_double_LED_warning) // 如果上一次闪烁是两灯报警, 则这一次单灯闪烁要保证另外一个灯是灭的
        {
            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET); // 灭灯2
            is_break_from_double_LED_warning = 0;
        }
        HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); // 切换灯1
    }
    else if (!is_received_H_normal && !is_received_T_normal) // 温湿度 都 异常
    {
        is_break_from_double_LED_warning = 1; // 标记为两灯报警
        // 液晶屏报警
        OLED_ShowString(96, 4, "!!!"); // t 温度
        OLED_ShowString(96, 6, "!!!"); // h 湿度

        // 使两个灯同步闪
        HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); // 灭灯1
        HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET); // 灭灯2

        HAL_Delay(50); // 等待灭灯完成

        HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); // 切换灯1
        HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin); // 切换灯2
    }
}

/**
 * @brief 为正常的温湿度数据取消闪烁报警
 */
void cancel_flash(void)
{
    // 液晶屏消除感叹号 !!! 报警
    OLED_ShowString(96, 4, "   "); // t 温度
    OLED_ShowString(96, 6, "   "); // h 湿度

    HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); // 灭灯1
    HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET); // 灭灯2
}
/* ----------------------------------------------温湿度相关函数 END-------------------------------------------- */

/* ----------------------------------------------USER CODE END 4-------------------------------------------- */

/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

    /** Configure the main internal regulator output voltage
     */
    HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
    /** Initializes the RCC Oscillators according to the specified parameters
     * in the RCC_OscInitTypeDef structure.
     */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
    RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV1;
    RCC_OscInitStruct.PLL.PLLN = 8;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
    RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        Error_Handler();
    }
    /** Initializes the CPU, AHB and APB buses clocks
     */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    {
        Error_Handler();
    }
    /** Initializes the peripherals clocks
     */
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1;
    PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK1;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
    {
        Error_Handler();
    }
}

/**
 * @brief  This function is executed in case of error occurrence.
 * @retval None
 */
void Error_Handler(void)
{
    /* USER CODE BEGIN Error_Handler_Debug */
    /* User can add his own implementation to report the HAL error return state */
    __disable_irq();
    while (1)
    {
    }
    /* USER CODE END Error_Handler_Debug */
}

#ifdef USE_FULL_ASSERT
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT CSTX AND THETA *****END OF FILE****/
